from fastapi import APIRouter, Depends, HTTPException, Query, Request
from sqlalchemy.orm import Session, selectinload
from sqlalchemy import text
from typing import List, Optional
from app.core.database import get_db
from app.models.character import Character
from app.models.taxonomy import Language, Personality, Skill
from app.schemas.character import CharacterSchema, CharacterSearchResponse, ChatRequest, ChatResponse, CharacterCreate, CharacterUpdate
from app.core.cache import cache_service
from app.core.rate_limit import limiter, get_rate_limit
from app.services.ollama_service import ollama_service
import logging

logger = logging.getLogger(__name__)
router = APIRouter()
def _get_or_create_by_name(db: Session, model, name: str):
    name_norm = (name or '').strip()
    if not name_norm:
        return None
    existing = db.query(model).filter(model.name == name_norm).first()
    if existing:
        return existing
    obj = model(name=name_norm)
    db.add(obj)
    db.flush()
    return obj

def _sync_m2m_relations(db: Session, character: Character, payload: CharacterCreate | CharacterUpdate):
    # 语言
    if getattr(payload, 'language', None) is not None:
        character.languages_rel.clear()
        for n in payload.language or []:
            obj = _get_or_create_by_name(db, Language, n)
            if obj:
                character.languages_rel.append(obj)
    # 性格
    if getattr(payload, 'personality', None) is not None:
        character.personalities_rel.clear()
        for n in payload.personality or []:
            obj = _get_or_create_by_name(db, Personality, n)
            if obj:
                character.personalities_rel.append(obj)
    # 技能
    if getattr(payload, 'skills', None) is not None:
        character.skills_rel.clear()
        for s in payload.skills or []:
            name = s.name if isinstance(s, dict) else getattr(s, 'name', None)
            desc = s.get('description') if isinstance(s, dict) else getattr(s, 'description', None)
            name_norm = (name or '').strip()
            if not name_norm:
                continue
            sk = db.query(Skill).filter(Skill.name == name_norm).first()
            if not sk:
                sk = Skill(name=name_norm, description=desc or None)
                db.add(sk)
                db.flush()
            character.skills_rel.append(sk)



@router.get("", response_model=List[CharacterSchema])
@limiter.limit(get_rate_limit("default"))
async def get_characters(
    request: Request,
    db: Session = Depends(get_db)
):
    """获取所有角色列表"""
    try:
        # 尝试从缓存获取
        cache_key = "characters:all"
        cached_data = cache_service.get(cache_key)
        if cached_data:
            logger.info("从缓存返回角色列表")
            return cached_data
        
        # 从数据库查询
        characters = db.query(Character).all()
        result = [CharacterSchema(**char.to_dict()) for char in characters]
        
        # 缓存结果（1小时）
        cache_service.set(cache_key, [char.dict() for char in result], expire=3600)
        
        logger.info(f"从数据库返回 {len(result)} 个角色")
        return result
        
    except Exception as e:
        logger.error(f"获取角色列表失败: {e}")
        raise HTTPException(status_code=500, detail="获取角色列表失败")


@router.post("", response_model=CharacterSchema)
@limiter.limit(get_rate_limit("default"))
async def create_character(
    request: Request,
    payload: CharacterCreate,
    db: Session = Depends(get_db)
):
    """创建角色并同步关联"""
    try:
        # 唯一性校验
        exists = db.query(Character).filter(Character.id == payload.id).first()
        if exists:
            raise HTTPException(status_code=400, detail="角色ID已存在")

        ch = Character(
            id=payload.id,
            name=payload.name,
            name_en=payload.nameEn,
            avatar=payload.avatar,
            description=payload.description,
            background=payload.background,
        )
        db.add(ch)
        db.flush()

        _sync_m2m_relations(db, ch, payload)
        db.commit()

        # 失效缓存
        cache_service.clear_pattern("characters:*")

        return CharacterSchema(**ch.to_dict())
    except HTTPException:
        db.rollback()
        raise
    except Exception as e:
        db.rollback()
        logger.error(f"创建角色失败: {e}")
        raise HTTPException(status_code=500, detail="创建角色失败")


@router.get("/search", response_model=CharacterSearchResponse)
@limiter.limit(get_rate_limit("characters_search"))
async def search_characters(
    request: Request,
    query: str = Query(..., description="搜索关键词", min_length=1, max_length=100),
    db: Session = Depends(get_db)
):
    """搜索角色"""
    try:
        # 验证查询参数
        if not query or not query.strip():
            return CharacterSearchResponse(characters=[], total=0)
        
        # 清理查询参数，防止SQL注入
        query = query.strip()[:100]  # 限制长度
        
        # 尝试从缓存获取
        cache_key = f"characters:search:{query}"
        cached_data = cache_service.get(cache_key)
        if cached_data:
            logger.info(f"从缓存返回搜索结果: {query}")
            return CharacterSearchResponse(**cached_data)
        
        # 优先使用 MySQL FULLTEXT（若可用），否则回退 LIKE
        characters = []
        try:
            # 使用原生SQL进行全文检索（布尔模式）
            sql = (
                "SELECT * FROM characters "
                "WHERE MATCH(name, name_en, description) AGAINST(:q IN BOOLEAN MODE)"
            )
            rows = db.execute(text(sql), {"q": f"{query}*"}).mappings().all()
            characters = [Character(**row) for row in rows]
        except Exception:
            # 回退到 LIKE 搜索
            search_term = f"%{query}%"
            characters = db.query(Character).filter(
                (Character.name.like(search_term)) |
                (Character.name_en.like(search_term)) |
                (Character.description.like(search_term))
            ).all()
        
        result_characters = [CharacterSchema(**char.to_dict()) for char in characters]
        result = CharacterSearchResponse(
            characters=result_characters,
            total=len(result_characters)
        )
        
        # 缓存结果（10分钟）
        cache_service.set(cache_key, result.dict(), expire=600)
        
        logger.info(f"从数据库返回搜索结果: {query}, 找到 {len(result_characters)} 个角色")
        return result
        
    except Exception as e:
        logger.error(f"搜索角色失败: {e}")
        raise HTTPException(status_code=500, detail="搜索角色失败")


@router.get("/{character_id}", response_model=CharacterSchema)
@limiter.limit(get_rate_limit("default"))
async def get_character(
    request: Request,
    character_id: str,
    db: Session = Depends(get_db)
):
    """根据角色ID获取角色详情"""
    try:
        # 尝试从缓存获取
        cache_key = f"characters:{character_id}"
        cached_data = cache_service.get(cache_key)
        if cached_data:
            logger.info(f"从缓存返回角色详情: {character_id}")
            return CharacterSchema(**cached_data)
        
        # 从数据库查询并显式加载关联，确保对话/详情包含规范化数据
        character = (
            db.query(Character)
            .options(
                selectinload(Character.languages_rel),
                selectinload(Character.personalities_rel),
                selectinload(Character.skills_rel),
            )
            .filter(Character.id == character_id)
            .first()
        )
        if not character:
            raise HTTPException(status_code=404, detail="角色不存在")
        
        result = CharacterSchema(**character.to_dict())
        
        # 缓存结果（30分钟）
        cache_service.set(cache_key, result.dict(), expire=1800)
        
        logger.info(f"从数据库返回角色详情: {character_id}")
        return result
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"获取角色详情失败: {e}")
        raise HTTPException(status_code=500, detail="获取角色详情失败")


@router.put("/{character_id}", response_model=CharacterSchema)
@limiter.limit(get_rate_limit("default"))
async def update_character(
    request: Request,
    character_id: str,
    payload: CharacterUpdate,
    db: Session = Depends(get_db)
):
    """更新角色并同步关联"""
    try:
        ch = db.query(Character).filter(Character.id == character_id).first()
        if not ch:
            raise HTTPException(status_code=404, detail="角色不存在")

        # 基本字段
        if payload.name is not None:
            ch.name = payload.name
        if payload.nameEn is not None:
            ch.name_en = payload.nameEn
        if payload.avatar is not None:
            ch.avatar = payload.avatar
        if payload.description is not None:
            ch.description = payload.description
        if payload.background is not None:
            ch.background = payload.background

        _sync_m2m_relations(db, ch, payload)
        db.commit()

        # 失效缓存
        cache_service.delete(f"characters:{character_id}")
        cache_service.clear_pattern("characters:all")
        cache_service.clear_pattern("characters:search:*")

        return CharacterSchema(**ch.to_dict())
    except HTTPException:
        db.rollback()
        raise
    except Exception as e:
        db.rollback()
        logger.error(f"更新角色失败: {e}")
        raise HTTPException(status_code=500, detail="更新角色失败")


@router.delete("/{character_id}")
@limiter.limit(get_rate_limit("default"))
async def delete_character(
    request: Request,
    character_id: str,
    db: Session = Depends(get_db)
):
    """删除角色（级联清理关联表）"""
    try:
        ch = db.query(Character).filter(Character.id == character_id).first()
        if not ch:
            raise HTTPException(status_code=404, detail="角色不存在")

        # 由于关联表外键 ON DELETE CASCADE，删除主记录将自动清理关联
        db.delete(ch)
        db.commit()

        # 失效缓存
        cache_service.delete(f"characters:{character_id}")
        cache_service.clear_pattern("characters:all")
        cache_service.clear_pattern("characters:search:*")

        return {"success": True}
    except HTTPException:
        db.rollback()
        raise
    except Exception as e:
        db.rollback()
        logger.error(f"删除角色失败: {e}")
        raise HTTPException(status_code=500, detail="删除角色失败")


@router.post("/{character_id}/chat", response_model=ChatResponse)
@limiter.limit(get_rate_limit("characters_chat"))
async def chat_with_character(
    request: Request,
    character_id: str,
    chat_request: ChatRequest,
    db: Session = Depends(get_db)
):
    """获取角色对话回复"""
    try:
        # 验证角色是否存在（显式加载关联，供提示词使用）
        character = (
            db.query(Character)
            .options(
                selectinload(Character.languages_rel),
                selectinload(Character.personalities_rel),
                selectinload(Character.skills_rel),
            )
            .filter(Character.id == character_id)
            .first()
        )
        if not character:
            raise HTTPException(status_code=404, detail="角色不存在")
        
        # 验证消息内容
        if not chat_request.message.strip():
            raise HTTPException(status_code=400, detail="消息内容不能为空")
        
        # 调用Ollama服务生成回复
        character_info = character.to_dict()
        response_data = await ollama_service.generate_response(
            character_info=character_info,
            user_message=chat_request.message,
            context=chat_request.context or []
        )
        
        # 创建ChatResponse对象
        chat_response = ChatResponse(**response_data)
        
        logger.info(f"角色 {character_id} 回复用户消息: {response_data.get('emotion', '未知')} - {response_data.get('action', '无动作')}")
        return chat_response
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"生成角色回复失败: {e}")
        raise HTTPException(status_code=500, detail="生成角色回复失败")
